using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content;
using System.Xml.Serialization;
using System.IO;
using Xfinity.Console;
using Microsoft.Xna.Framework.Input;
using Xfinity.Engine;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using System.Xml.Schema;
using System.Xml;
using System.ComponentModel;
using System.Reflection;

namespace Xfinity.Physics
{
    /// <summary>
    /// A material describing the properties of a surface.
    /// </summary>
    [Serializable]
    public class Material : IDisposable
    {
        public class MaterialReader : ContentTypeReader<Material>
        {
            protected override Material Read(ContentReader input, Material existingInstance)
            {
                Material m = input.ReadObject<Material>(); 
                m.OnDeserialization();
                return m;
            }
        }
        #region MaterialSerializer
        public class MaterialSerializer : XmlSerializer
        {
            [DebuggerStepThrough]
            public MaterialSerializer()
                : base(typeof(Material))
            {

            }
            [DebuggerStepThrough]
            public new Material Deserialize(Stream stream)
            {
                Material m = (Material)base.Deserialize(stream);

                m.OnDeserialization();
                return m;
            }
        }
        #endregion

        [BindCommand("CreateTexture")]
        public static void CreateTexture(string[] commands)
        {
            Material m = new Material();

            m.DetailDiffuseMapName = "LAWL";
            m.DetailHeightMapName = "lol 2";
            m.DetailNormalMapName = "lol3;";
            m.diffuseMapName = "grass";
            m.EffectName = "BasicEffect";
            m.EmissiveMapName = "flat";
            m.Name = "fucked up!!!";
            m.ParallaxMapName = "psychadellic";
            m.ambient = new Color(0, 0, 255, 0);
            m.EmissiveColour = new Color(1, 2, 3, 4);
            m.SpecularMapName = "heh";


            m.Serialize();
            m.ParallaxMapName = "OH NOES";

            //Material mat = content.Load<Material>("\\Content\\Materials\\mat");

            MaterialSerializer serializer = new MaterialSerializer();
            FileStream s = File.OpenRead(@".\Content\Materials\" + m.name + ".material");
            Material loaded = serializer.Deserialize(s);
            s.Close();
            s.Dispose();
        }

        private void Serialize()
        {
            MaterialSerializer serializer = new MaterialSerializer();

            using (FileStream s = File.Open(@".\Content\Materials\" + name + ".material", FileMode.Create))
            {
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.OmitXmlDeclaration = true;
                using (XmlWriter x = XmlWriter.Create(s, settings))
                {
                    serializer.Serialize(x, this);
                }
            }
        }

        [DebuggerStepThrough]
        public Material()
        {

        }

        private void AddMaterialToMaterialList()
        {
            if (!matList.ContainsKey(name))
            {
                matList.Add(name, this);
            }
        }
        private static FileSystemWatcher watcher;
        static Material()
        {
            CreateHotLoader();
        }
        private static Dictionary<string, Material> matList = new Dictionary<string, Material>();
        [Conditional("DEBUG")]
        private static void CreateHotLoader()
        {
            string path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), Xfinity.Resources.Resources.MaterialPath);
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            watcher = new FileSystemWatcher(path);
            watcher.EnableRaisingEvents = true;
            watcher.Changed += MaterialChanged;
        }

        private static void MaterialChanged(object sender, FileSystemEventArgs e)
        {
            watcher.EnableRaisingEvents = false;
            foreach (KeyValuePair<string, Material> mat in matList)
            {
                if (e.Name == mat.Key + ".material")
                {
                    Material material;
                    using (FileStream stream = File.OpenRead(e.FullPath))
                    {
                        MaterialSerializer m = new MaterialSerializer();
                        material = m.Deserialize(stream) as Material;
                        stream.Close();
                    }
                    FieldInfo[] fields = typeof(Material).GetFields(BindingFlags.NonPublic | BindingFlags.Instance);

                    //there is no need to check for invalid data, as it wouldn't have been serialised otherwise
                    foreach (FieldInfo field in fields)
                    {
                        object value = field.GetValue(material);
                        field.SetValue(mat.Value, value);
                    }
                }
            }
            watcher.EnableRaisingEvents = true;

        }

        #region Material properties
        private string name;

        /// <summary>
        /// 
        /// </summary>
        public string Name
        {
            [DebuggerStepThrough]
            get { return name; }
            [DebuggerStepThrough]
            set { name = value; }
        }
        [NonSerialized]
        private Effect effect;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Effect Effect
        {
            [DebuggerStepThrough]
            get { return effect; }
            [DebuggerStepThrough]
            set { effect = value; }
        }
        private string effectName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("Effect")]
        public string EffectName
        {
            [DebuggerStepThrough]
            get { return effectName; }
            [DebuggerStepThrough]
            set { effectName = value; }
        }
        [NonSerialized]
        private Color ambient;

        /// <summary>
        /// 
        /// </summary>
        public Color AmbientColour
        {
            [DebuggerStepThrough]
            get { return ambient; }
            [DebuggerStepThrough]
            set { ambient = value; }
        }
        [NonSerialized]
        private Color diffuseColour;

        /// <summary>
        /// 
        /// </summary>
        public Color DiffuseColour
        {
            [DebuggerStepThrough]
            get { return diffuseColour; }
            [DebuggerStepThrough]
            set { diffuseColour = value; }
        }
        [NonSerialized]
        private Color specularColour;

        /// <summary>
        /// 
        /// </summary>
        public Color SpecularColour
        {
            [DebuggerStepThrough]
            get { return specularColour; }
            [DebuggerStepThrough]
            set { specularColour = value; }
        }
        [NonSerialized]
        private Color emissiveColour;

        /// <summary>
        /// 
        /// </summary>
        public Color EmissiveColour
        {
            [DebuggerStepThrough]
            get { return emissiveColour; }
            [DebuggerStepThrough]
            set { emissiveColour = value; }
        }
        [NonSerialized]
        private Texture2D specularMap;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Texture2D SpecularMap
        {
            [DebuggerStepThrough]
            get { return specularMap; }
            [DebuggerStepThrough]
            set { specularMap = value; }
        }
        private string specularMapName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("SpecularMap")]
        public string SpecularMapName
        {
            [DebuggerStepThrough]
            get { return specularMapName; }
            [DebuggerStepThrough]
            set { specularMapName = value; }
        }

        [NonSerialized]
        private Texture2D detailHeightMap;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Texture2D DetailHeightMap
        {
            [DebuggerStepThrough]
            get { return detailHeightMap; }
            [DebuggerStepThrough]
            set { detailHeightMap = value; }
        }

        private string detailHeightMapName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("DetailHeightMap")]
        public string DetailHeightMapName
        {
            [DebuggerStepThrough]
            get { return detailHeightMapName; }
            [DebuggerStepThrough]
            set { detailHeightMapName = value; }
        }

        [NonSerialized]
        private Texture2D detailNormalMap;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Texture2D DetailNormalMap
        {
            [DebuggerStepThrough]
            get { return detailNormalMap; }
            [DebuggerStepThrough]
            set { detailNormalMap = value; }
        }
        private string detailNormalMapName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("DetailNormalMap")]
        public string DetailNormalMapName
        {
            [DebuggerStepThrough]
            get { return detailNormalMapName; }
            [DebuggerStepThrough]
            set { detailNormalMapName = value; }
        }

        [NonSerialized]
        private Texture2D detailDiffuseMap;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Texture2D DetailDiffuseMap
        {
            [DebuggerStepThrough]
            get { return detailDiffuseMap; }
            [DebuggerStepThrough]
            set { detailDiffuseMap = value; }
        }

        private string detailDiffuseMapName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("DetailDiffuseMap")]
        public string DetailDiffuseMapName
        {
            [DebuggerStepThrough]
            get { return detailDiffuseMapName; }
            [DebuggerStepThrough]
            set { detailDiffuseMapName = value; }
        }

        [NonSerialized]
        private Texture2D parallaxMap;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Texture2D ParallaxMap
        {
            [DebuggerStepThrough]
            get { return parallaxMap; }
            [DebuggerStepThrough]
            set { parallaxMap = value; }
        }
        private string parallaxMapName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("ParallaxMap")]
        public string ParallaxMapName
        {
            [DebuggerStepThrough]
            get { return parallaxMapName; }
            [DebuggerStepThrough]
            set { parallaxMapName = value; }
        }

        [NonSerialized]
        private Texture2D diffuseMap;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Texture2D DiffuseMap
        {
            [DebuggerStepThrough]
            get { return diffuseMap; }
            [DebuggerStepThrough]
            set { diffuseMap = value; }
        }
        private string diffuseMapName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("DiffuseMap")]
        public string DiffuseMapName
        {
            [DebuggerStepThrough]
            get { return diffuseMapName; }
            [DebuggerStepThrough]
            set { diffuseMapName = value; }
        }
        [NonSerialized]
        private Texture2D emissiveMap;

        /// <summary>
        /// 
        /// </summary>
        [XmlIgnore]
        [ContentSerializerIgnore]
        public Texture2D EmissiveMap
        {
            [DebuggerStepThrough]
            get { return emissiveMap; }
            [DebuggerStepThrough]
            set { emissiveMap = value; }
        }

        private string emissiveMapName;

        /// <summary>
        /// 
        /// </summary>
        [XmlElement("EmissiveMap")]
        public string EmissiveMapName
        {
            [DebuggerStepThrough]
            get { return emissiveMapName; }
            [DebuggerStepThrough]
            set { emissiveMapName = value; }
        }

        private float specularPower;

        /// <summary>
        /// 
        /// </summary>
        public float SpecularPower
        {
            [DebuggerStepThrough]
            get { return specularPower; }
            [DebuggerStepThrough]
            set { specularPower = value; }
        }
        #endregion


        public void ApplyToModelMesh(ModelMesh mesh)
        {
            foreach (ModelMeshPart mmp in mesh.MeshParts)
            {
                mmp.Effect = effect;
            }
        }

        private static ContentManager content;
        private static readonly string texturePath = Xfinity.Resources.Resources.TexturePath;
        private static readonly string effectPath = Xfinity.Resources.Resources.EffectPath;

        private void OnDeserialization()
        {
            content = GameManager.Content;
            
            LoadTextures();

            if (effectName.ToLower() != "basiceffect")
            {
                LoadItem<Effect>(out effect, effectName, effectPath);
                if (effect != null)
                {
                    SetStandardSemantics();
                }
            }
            else
            {
                #region BasicEffect
                effect = new BasicEffect(GraphicsHelper.GraphicsDevice, GraphicsHelper.EffectPool);
                BasicEffect e = effect as BasicEffect;
                e.PreferPerPixelLighting = true;
                if (diffuseMap != null)
                {
                    e.Texture = diffuseMap;
                    e.TextureEnabled = true;
                }

                e.EnableDefaultLighting();

                if (CheckColourIsNotTransparentBlack(ambient))
                {
                    e.AmbientLightColor = ambient.ToVector3();
                }
                if (CheckColourIsNotTransparentBlack(diffuseColour))
                {
                    e.DiffuseColor = diffuseColour.ToVector3();
                }
                if (CheckColourIsNotTransparentBlack(specularColour))
                {
                    e.SpecularColor = specularColour.ToVector3();
                }
                if (specularPower != 0)
                {
                    e.SpecularPower = specularPower;
                }
                if (CheckColourIsNotTransparentBlack(emissiveColour))
                {
                    e.EmissiveColor = emissiveColour.ToVector3();
                }

                #endregion
            }
            AddMaterialToMaterialList();
        }

        private void SetStandardSemantics()
        {
            SetEffectTexture("DIFFUSEMAP", diffuseMap);
            SetEffectTexture("SPECULARMAP", specularMap);
            SetEffectTexture("PARALLAXMAP", parallaxMap);
            SetEffectTexture("DETAILNORMALMAP", detailNormalMap);
            SetEffectTexture("DETAILDIFFUSEMAP", detailDiffuseMap);
            SetEffectTexture("DETAILHEIGHTMAP", detailHeightMap);
            SetEffectTexture("EMISSIVEMAP", emissiveMap);
            SetEffectColour("DIFFUSE", diffuseColour);
            SetEffectColour("SPECULAR", specularColour);
            SetEffectColour("AMBIENT", ambient);
            SetEffectFloat("SPECULARPOWER", specularPower);
        }

        [DebuggerStepThrough]
        private void SetEffectFloat(string semantic, float value)
        {
            EffectParameter p = effect.Parameters.GetParameterBySemantic(semantic);
            if (p != null)
                p.SetValue(value);
        }

        [DebuggerStepThrough]
        private static bool CheckColourIsNotTransparentBlack(Color c)
        {
            return c != Color.TransparentBlack;

        }

        [DebuggerStepThrough]
        private void LoadTextures()
        {
            LoadItem<Texture2D>(out detailDiffuseMap, detailDiffuseMapName, texturePath);
            LoadItem<Texture2D>(out detailHeightMap, detailHeightMapName, texturePath);
            LoadItem<Texture2D>(out detailNormalMap, detailNormalMapName, texturePath);
            LoadItem<Texture2D>(out diffuseMap, diffuseMapName, texturePath);
            LoadItem<Texture2D>(out emissiveMap, emissiveMapName, texturePath);
            LoadItem<Texture2D>(out parallaxMap, parallaxMapName, texturePath);
            LoadItem<Texture2D>(out specularMap, specularMapName, texturePath);
        }

        [DebuggerStepThrough]
        private void SetEffectColour(string semantic, Color value)
        {
            EffectParameter p = effect.Parameters.GetParameterBySemantic(semantic);
            if (p != null)
                p.SetValue(value.ToVector4());
            else
                p = effect.Parameters[semantic];
            if (p != null)
            {
                p.SetValue(value.ToVector4());
            }
        }

        [DebuggerStepThrough]
        private void SetEffectTexture(string semantic, Texture value)
        {
            if (value == null)
                return;

            EffectParameter p = effect.Parameters.GetParameterBySemantic(semantic);
            if (p != null)
                p.SetValue(value);
            else
                p = effect.Parameters[semantic];
            if (p != null)
            {
                p.SetValue(value);
            }

        }

        [DebuggerStepThrough]
        private static void LoadItem<T>(out T contentToLoad, string item, string typePath)
        {
            string currentPath = Path.Combine(typePath, item);
            if (File.Exists(currentPath))
            {
                contentToLoad = content.Load<T>(currentPath);
            }
            else
            {
                StandardConsole.Fail("Cannot find " + currentPath);
                contentToLoad = default(T);
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            foreach (FieldInfo field in typeof(Material).GetFields())
            {
                if (field.GetType().IsSubclassOf(typeof(IDisposable)))
                {
                    field.GetType().InvokeMember("Dispose", BindingFlags.InvokeMethod, null, field.GetValue(this), null);
                }
            }
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}